#include <sys/syscall.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <dirent.h>
#include <execinfo.h>
#include <algorithm>
#include <regex>
#include <boost/regex.hpp>
#include <inttypes.h>
#include <signal.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/aes.h>
#include <openssl/pkcs7.h>

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>

#include "util.h"
#include "log.h"
#include "fiber.h"

static qtch::Logger::ptr logger= QTCH_LOG_NAME("system");
namespace qtch{

pid_t GetThreadId(){
    return syscall(SYS_gettid);
}

uint64_t GetFiberId(){
    return qtch::Fiber::getFiberId();
}

uint64_t GetCurrentMS(){
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000ul  + tv.tv_usec / 1000;
}

std::string Time2Str(time_t ts, const std::string& format) {
    struct tm t;
    localtime_r(&ts,&t);
    char buf[64];
    strftime(buf,sizeof(buf), format.c_str(), &t);
    return buf;
}

time_t Str2Time(const char* str,const char* format){
    struct tm t;
    memset(&t, 0, sizeof(t));
    if(!strptime(str, format, &t)) {
        return 0;
    }
    return mktime(&t);
}

std::string Gmtime2Str(time_t ts, const std::string& format) {
    struct tm t;
    gmtime_r(&ts,&t);
    char buf[64];
    strftime(buf,sizeof(buf), format.c_str(), &t);
    return buf;
}

static int __lstat(const char* file, struct stat* st = nullptr) {
    struct stat lst;
    int ret = lstat(file, &lst);
    if(st) {
        *st = lst;
    }
    return ret;
}

void Backtrace(std::vector<std::string>& bt, int size, int skip){
    skip = skip < 0 ? 0:skip;
    void ** array = (void **)malloc(sizeof(void*) * size);
    size_t s = backtrace(array,size);

    char ** strings = backtrace_symbols(array,s);
    if(strings == NULL){
        QTCH_LOG_ERROR(logger) << "backtrace_symbols error";
    }
    else{
        for(size_t i = skip; i<s;++i){
            bt.push_back(strings[i]);
        }
        free(strings);
    }
    free(array);

}

std::string BacktraceToString(int size,int skip,const std::string prefix){
    std::vector<std::string> bt;
    Backtrace(bt,size,skip);
    std::stringstream ss;
    for(size_t i=0;i<bt.size();++i){
        ss << prefix << bt[i] << std::endl;
    }
    return ss.str();
}

bool FSUtil::isFile(const std::string& fileName){
    struct stat buf;
    return (stat(fileName.c_str(), &buf)==0&&S_ISREG(buf.st_mode));
}

bool FSUtil::isDir(const std::string& dirName){
    struct stat buf;
    return (stat(dirName.c_str(), &buf)==0&&S_ISDIR(buf.st_mode));
}

bool FSUtil::listAllFiles(std::vector<std::string>& files,const std::string& folderPath,const std::string& subfix,bool recursive){
    if(!isDir(folderPath)){
        QTCH_LOG_INFO(logger) << "listAllFiles find path: " <<folderPath << " it not dir" ;
        return false;
    }
    if(access(folderPath.c_str(),0) != 0){
        QTCH_LOG_INFO(logger) << "dir path: " <<folderPath << " not existed" ;
        return false;
    }
    DIR *pDir = opendir(folderPath.c_str());
    if(pDir==NULL){
        QTCH_LOG_ERROR(logger) << "opendir " << folderPath << " faile, errno=" << errno << " reason:" << strerror(errno);
        return false;
    }
    struct dirent * pEnt = NULL;
    while((pEnt = readdir(pDir))!=NULL){
        if(pEnt->d_type == DT_DIR){
            if(!strcmp(pEnt->d_name,".")||!strcmp(pEnt->d_name,"..")){
                continue;
            }
            if(recursive){
                listAllFiles(files,folderPath+"/"+std::string(pEnt->d_name),subfix);
            }
        }
        else{
            if(subfix.empty()){
                files.push_back(folderPath+"/"+std::string(pEnt->d_name));
            }
            else{
                std::string fileName(pEnt->d_name);
                if(fileName.substr(strlen(pEnt->d_name)-subfix.length())==subfix){
                    files.push_back(folderPath+"/"+std::string(pEnt->d_name));
                }
            }
        }
    }
    return true;
}

bool FSUtil::__mkdir(const std::string& folderPath){
    if(access(folderPath.c_str(),0)!=0){
        QTCH_LOG_INFO(logger) << "mkdir new dir:" << folderPath;
        return mkdir(folderPath.c_str(),S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)==0;
    }
    return true;
}

bool FSUtil::MkDir(const std::string& folderPath){
    if(access(folderPath.c_str(),0)==0){
        return true;
    }
    char * path = strdup(folderPath.c_str());
    char * chr = strchr(path+1,'/');
    for(;chr;*chr = '/',chr = strchr(chr+1,'/')){
        *chr = '\0';
        if(!__mkdir(path)){
            break;
        }
    }
    bool res=false;
    if(chr == nullptr){
        if(__mkdir(path)){
            res = true;
        }
    }
    free(path);
    return res;
}

bool FSUtil::Unlink(const std::string& filename, bool exist){
    if(!exist && __lstat(filename.c_str())){
        return true;
    }
    return ::unlink(filename.c_str())==0;
}

bool FSUtil::isRunningPidFile(const std::string& pidFile) {
    pid_t pid = getRunningPid(pidFile);
    if(pid < 1){
        return false;
    }
    if(kill(pid,0) != 0){
        return false;
    }
    return true;
}

pid_t FSUtil::getRunningPid(const std::string& pidFile){
    if(__lstat(pidFile.c_str())!=0){
        return -1;
    }
    std::ifstream ifs(pidFile);
    std::string lines;
    if(!ifs || !std::getline(ifs,lines)){
        return -1;
    }
    if(lines.empty()){
        return -1;
    }
    pid_t pid = TypeUtil::Atoi(lines);
    return pid;
}

std::string FSUtil::BaseName(const std::string& filename){
    if(filename.empty()){
        return filename;
    }
    auto pos = filename.rfind('/');
    if(pos == std::string::npos){
        return filename;
    }
    return filename.substr(pos + 1);
}

std::string StringUtil::trim(const std::string& str,const std::string& delimit){
    auto begin = str.find_first_not_of(delimit);
    if(begin == std::string::npos){
        return "";
    }
    auto end = str.find_last_not_of(delimit);
    return str.substr(begin, end - begin + 1);
}

std::vector<std::string> StringUtil::split(const std::string& str, char delim, size_t max){
    std::vector<std::string> result;
    if(str.empty()){
        return result;
    }
    size_t last = 0;
    size_t pos = str.find(delim);
    while(pos != std::string::npos){
        result.push_back(str.substr(last, pos - last));
        last = pos + 1;
        if(--max == 1){
            break;
        }
        pos = str.find(delim,last);
    }
    result.push_back(str.substr(last));
    return result;
}

std::vector<std::string> StringUtil::split(const std::string& str, const char* delim, size_t max){
    std::vector<std::string> result;
    if(str.empty()){
        return result;
    }
    size_t last = 0;
    size_t pos = str.find_first_of(delim);
    while(pos != std::string::npos){
        result.push_back(str.substr(last, pos - last));
        last = pos + 1;
        if(--max == 1){
            break;
        }
        pos = str.find_first_of(delim,last);
    }
    result.push_back(str.substr(last));
    return result;
}

std::string StringUtil::replace(const std::string& src, char find, char replaceWitch){
    auto str1 = src;
    size_t pos = str1.find(find);
    while(pos != std::string::npos){
        str1[pos] = replaceWitch;
        pos = str1.find(find,pos + 1);
    }
    return str1;
}

std::string StringUtil::replace(const std::string& src, char find, const std::string& replaceWitch){
    std::stringstream ss;
    size_t index = 0;
    size_t pos = src.find(find);
    while(pos != std::string::npos){
        ss << src.substr(index,pos - index);
        ss << replaceWitch;
        index = pos + 1;
        pos = src.find(find,index);
    }
    ss << src.substr(index);
    return ss.str();
}

std::string StringUtil::toLower(const std::string& v) {
    std::string rt = v;
    std::transform(rt.begin(),rt.end(),rt.begin(), ::tolower);
    return rt;
}

std::string StringUtil::toUpper(const std::string& v) {
    std::string rt = v;
    std::transform(rt.begin(),rt.end(),rt.begin(), ::toupper);
    return rt;
}

std::vector<std::string> StringUtil::regexSearch(const std::string& src,const std::string& pattern) {
    boost::regex regexPattern(pattern);
    std::vector<std::string> result;
    boost::sregex_token_iterator iter (src.begin(),src.end(),regexPattern);
    while(iter != boost::sregex_token_iterator()){
        result.push_back(*iter);
        ++iter;
    }

    return result;

}

bool StringUtil::regexMatch(const std::string& src,const std::string& pattern){
    boost::regex regexPattern(pattern);
    bool match = boost::regex_match(src,regexPattern);
    return match;
}

std::string StringUtil::regexReplace(const std::string& src, const std::string& pattern, const std::string& replaceWitch) {
    boost::regex regexPattern(pattern);
    std::string result = boost::regex_replace(src,regexPattern,replaceWitch);
    return result;
}


int64_t TypeUtil::Atoi(const std::string& str) {
    if(str.empty()){
        return 0;
    }
    return Atoi(str.c_str());
}

double TypeUtil::Atof(const std::string& str) {
    if(str.empty()){
        return 0.0;
    }
    return Atof(str.c_str());
}

int64_t TypeUtil::Atoi(const char* str) {
    if(str==nullptr){
        return 0;
    }
    return strtoull(str,nullptr,10);
}

double TypeUtil::Atof(const char* str) {
    if(str==nullptr){
        return 0.0;
    }
    return atof(str);
}

std::string TypeUtil::ItoA(int64_t v) {
    char temp[30];
    sprintf(temp,"%" PRId64,v);
    std::string result = std::string(temp);
    return result;
}

std::string TypeUtil::ItoA(uint64_t v) {
    char temp[30];
    sprintf(temp,"%" PRIu64,v);
    std::string result = std::string(temp);
    return result;
}

std::string TypeUtil::Itof(double v){
    char temp[30];
    sprintf(temp,"%lf",v);
    std::string result = std::string(temp);
    return result;
}

std::string TypeUtil::Itof(float v){
    char temp[30];
    sprintf(temp,"%f",v);
    std::string result = std::string(temp);
    return result;
}

std::string StringUtil::Format(const char* fmt,...) {
    va_list ap;
    va_start(ap,fmt);
    auto v = Formatv(fmt,ap);
    va_end(ap);
    return v;
}

std::string StringUtil::Formatv(const char* fmt, va_list ap) {
    char * result = nullptr;
    int len = vasprintf(&result,fmt,ap);
    if(len==-1){
        return "";
    }
    std::string b(result);
    free(result);
    return b;
}

std::string CypherUtil::encodeBase64(const std::string& input) {
    return encodeBase64(input.c_str(),input.length());
}

std::string CypherUtil::decodeBase64(const std::string& input) {
    return decodeBase64(input.c_str(),input.length());
}

std::string CypherUtil::encodeBase64(const char* input,int input_len) {
    static const char* code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    std::string output;
    int input_num;
    int den_num = 0;
    for(int i=0;i<input_len;i+=3){
        input_num = ((int)input[i])<<16;
        if(i+1<input_len){
            input_num += ((int)input[i+1])<<8;
        }else{
            den_num++;
        }
        if(i+2 < input_len){
            input_num += ((int)input[i+2]);
        }else{
            den_num++;
        }
        if(den_num){
            break;
        }
        char tmp[5];
        tmp[0] = code[((int)(input_num>>18)& 0x3f)];
        tmp[1] = code[((int)(input_num>>12)& 0x3f)];
        tmp[2] = code[((int)(input_num>>6)& 0x3f)]; 
        tmp[3] = code[((int)(input_num)& 0x3f)];
        tmp[4] = '\0';
        output.append(tmp);
    }
    den_num = 4 - den_num;
    if(den_num){
        char tmp[5];
        for(int i=0;i<=3;++i){
            if(den_num>0){
                tmp[i] = code[((int)(input_num>>(18-6*i))& 0x3f)];
            }else{
                tmp[i] = '=';
            }
            den_num--;
        }
        tmp[4] = '\0';
        output.append(tmp);
    }
    return output;
    
}

int base64IndexOfCode(const char c){
    if(c>='A' && c <= 'Z'){
        return c-'A';
    }else if(c>='a'&& c <= 'z'){
        return c-'a'+26;
    }else if(c>='0'&& c <='9'){
        return c-'0'+52;
    }else if(c=='+'){
        return 62;
    }else if(c == '/'){
        return 63;
    }else{
        return 0;
    }
}

std::string CypherUtil::decodeBase64(const char* input,int input_len) {
    std::string output;
    char tmp[4];
    int k=0;
    int num = 0;
    for(int i=0;i<input_len;++i){
        if(input[i]=='='){
            break;
        }
        num += (base64IndexOfCode(input[i]) <<  (18-k*6));
        k++;
        if(k==4){
            tmp[0] = (char)((num>>16)& 0xFf);
            tmp[1] = (char)((num>>8)&0xFf);
            tmp[2] = (char)((num)&0xFf);
            tmp[3] = '\0';
            output.append(tmp);
            k = 0;
            num = 0;
        }
    }
    if(k){
        int index = 0;
        for(int i=0;i<k;++i){
            tmp[index++] = (char)((num>>(16 - 8*i))&0xFf);
        }
        tmp[index] = '\0';
        output.append(tmp);
    }
    return output;

}

std::string StringUtil::hexstring_from_data(const std::string& input){
    return hexstring_from_data(input.c_str(),input.size());
}

std::string StringUtil::hexstring_from_data(const void* data,size_t len){
    if(len == 0){
        return std::string();
    }
    const unsigned char * p = (const unsigned char*)data;
    std::string result;
    result.resize(len*2);
    for(size_t i=0;i<len;++i){
        char t1 = (p[i]>>4)& 0xf;
        char t2 = p[i] & 0xf;
        if(t1<=9){
            result[i*2] = '0'+t1;
        }else{
            t1 -= 10;
            result[i*2] = 'A' + t1;
        }
        if(t2<=9){
            result[i * 2 + 1] = '0'+t2;
        }else{
            t2 -= 10;
            result[i * 2 + 1] = 'A' + t2;
        }
    }
    return result;
}

std::string StringUtil::data_from_hexstring(const std::string& input) {
    return data_from_hexstring(input.c_str(),input.size());
}

std::string StringUtil::data_from_hexstring(const void* data,size_t len) {
    if(len==0){
        return std::string();
    }
    if(len%2){
        throw std::invalid_argument("data_from_hexstring len%2 != 0");
    }
    std::string result;
    result.resize(len/2);
    const char* buf = (const char*)data;
    int j = 0;
    for(size_t i=0;i<len;i+=2){
        if(buf[i]>='a'&& buf[i]<='z'){
            result[j] = (buf[i]-'a'+10) << 4;
        }else if(buf[i]>='A'&&buf[i]<='Z'){
            result[j] = (buf[i]-'A'+10) << 4;
        }else if(buf[i]>='0'&&buf[i]<='9'){
            result[j] = (buf[i]-'0') << 4;
        }else{
            throw std::invalid_argument("data_from_hexstring data not hex");
        }
        j++;

        if(buf[i+1]>='a'&& buf[i+1]<='z'){
            result[j] = (buf[i+1]-'a'+10);
        }else if(buf[i+1]>='A'&&buf[i+1]<='Z'){
            result[j] = (buf[i+1]-'A'+10);
        }else if(buf[i+1]>='0'&&buf[i+1]<='9'){
            result[j] = (buf[i+1]-'0');
        }else{
            throw std::invalid_argument("data_from_hexstring data not hex");
        }
        j++;
    }
    return result;
}

std::string StringUtil::rand_string(size_t len,const std::string& chars){
    if(len == 0 || chars.empty()){
        return "";
    }
    std::string rt;
    size_t count = chars.size();
    rt.resize(len);
    for(size_t i=0;i<len;i++){
        rt[i] = chars[rand() % count];
    }
    return rt;
}

std::string HashUtil::md5Sum(const std::string& input){
    return md5Sum(input.c_str(),input.size());
}

std::string HashUtil::md5Sum(const char* input,size_t input_len){
    MD5_CTX ctx;
    MD5_Init(&ctx);
    MD5_Update(&ctx,input,input_len);
    std::string result;
    result.resize(MD5_DIGEST_LENGTH);
    MD5_Final((unsigned char *)&result[0],&ctx);
    return StringUtil::hexstring_from_data(result);
}

std::string HashUtil::sha256(const std::string& input) {
    return sha256(input.c_str(),input.size());
}

std::string HashUtil::sha256(const char* input,size_t input_len) {
    std::string result;
    result.resize(SHA256_DIGEST_LENGTH);
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, input, input_len);
    SHA256_Final((unsigned char *)&result[0], &sha256);
    return result;
}

std::string HashUtil::hmacSha256(const std::string& key,const std::string& input) {
    return hmacSha256(key.c_str(),key.size(),input.c_str(),input.size());
}

std::string HashUtil::hmacSha256(const char* key,size_t key_len,const char* input,size_t input_len) {
    std::string result;
    result.resize(32);
    HMAC_CTX *h;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX hmac;
    HMAC_CTX_init(&hmac);
    h = &hmac;
#else
    h = HMAC_CTX_new();
#endif
    
    HMAC_Init_ex(h, key, key_len, EVP_sha256(), NULL);
    HMAC_Update(h, ( unsigned char* )input, input_len);
    unsigned int len = 32;
    HMAC_Final(h, (unsigned char *)&result[0], &len);
    if(len!=32){
        QTCH_LOG_WARN(logger) << "len=" << len;
    }
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    HMAC_CTX_cleanup(h);
#else
    HMAC_CTX_free(h);
#endif
    return result;
}

std::string HashUtil::sha1(const std::string& input) {
    return sha1(input.c_str(),input.size());
}

std::string HashUtil::sha1(const char* input,size_t input_len) {
    std::string result;
    result.resize(SHA_DIGEST_LENGTH);

    SHA_CTX sha1;
    SHA1_Init(&sha1);
    int rt = SHA1_Update(&sha1, input, input_len);
    if(!rt){
        return "";
    }
    rt = SHA1_Final((unsigned char *)&result[0], &sha1);
    if(!rt){
        return "";
    }
    return result;
}

std::string CypherUtil::encodeAES_cbc(const std::string& input,const std::string key) {
    return encodeAES_cbc(input.c_str(),input.size(),key.c_str(),key.size());
}

std::string CypherUtil::decodeAES_cbc(const std::string& input,const std::string key) {
    return decodeAES_cbc(input.c_str(),input.size(),key.c_str(),key.size());
}

std::string CypherUtil::encodeAES_cbc(const char* input,size_t input_len,const char* key,size_t key_len) {
    AES_KEY aesKey;
    AES_set_encrypt_key((const unsigned char *)key, 8 * key_len, &aesKey);
    int padding = key_len - (input_len % key_len);

    unsigned char * temp = (unsigned char*)malloc( input_len + padding);
    if(temp==NULL){
        return "";
    }
    memcpy(temp,input,input_len);
    memset( temp + input_len, padding, padding );

    unsigned char * out = (unsigned char*)malloc( input_len + padding);
    if(out==NULL){
        free(temp);
        return "";
    }
    unsigned char iv[16] = { 0 };
    AES_cbc_encrypt(temp, out,input_len + padding,  &aesKey, iv, AES_ENCRYPT);
    std::string result((char*)out,(size_t)(input_len + padding));
    free(temp);
    free(out);
    return result;
}

std::string CypherUtil::decodeAES_cbc(const char* input,size_t input_len,const char* key,size_t key_len) {
    AES_KEY aesKey;
    AES_set_decrypt_key((const unsigned char *)key, 8 * key_len, &aesKey);
    unsigned char * out = (unsigned char*)malloc( input_len);
    if(out==NULL){
        return "";
    }
    unsigned char iv[16] = { 0 };
    AES_cbc_encrypt((const unsigned char *)input, out,input_len,  &aesKey, iv, AES_DECRYPT);
    std::string result;
    if(out[input_len-1]>0 && out[input_len-1] < key_len && input_len - out[input_len-1] > 0){
        result.append((char*)out,(size_t)(input_len - out[input_len-1]));
    }
    else{
        return "";
    }

    free(out);
    return result;
}

static const char uri_chars[256] = {
    /* 0 */
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 1, 1, 0,
    1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 0, 0, 0, 1, 0, 0,
    /* 64 */
    0, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 0, 0, 0, 0, 1,
    0, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 0, 0, 0, 1, 0,
    /* 128 */
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    /* 192 */
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
};

static const char xdigit_chars[256] = {
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
    0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};

#define CHAR_IS_UNRESERVED(c)           \
    (uri_chars[(unsigned char)(c)])

std::string CypherUtil::encodeUrl(const std::string& input, bool space_as_plus) {
    static const char *hexdigits = "0123456789ABCDEF";
    std::string* ss = nullptr;
    const char* end = input.c_str() + input.length();
    for(const char* c = input.c_str() ; c < end; ++c) {
        if(!CHAR_IS_UNRESERVED(*c)) {
            if(!ss) {
                ss = new std::string;
                ss->reserve(input.size() * 1.2);
                ss->append(input.c_str(), c - input.c_str());
            }
            if(*c == ' ' && space_as_plus) {
                ss->append(1, '+');
            } else {
                ss->append(1, '%');
                ss->append(1, hexdigits[(uint8_t)*c >> 4]);
                ss->append(1, hexdigits[*c & 0xf]);
            }
        } else if(ss) {
            ss->append(1, *c);
        }
    }
    if(!ss) {
        return input;
    } else {
        std::string rt = *ss;
        delete ss;
        return rt;
    }
}

std::string CypherUtil::decodeUrl(const std::string& input, bool space_as_plus) {
    std::string* ss = nullptr;
    const char* end = input.c_str() + input.length();
    for(const char* c = input.c_str(); c < end; ++c) {
        if(*c == '+' && space_as_plus) {
            if(!ss) {
                ss = new std::string;
                ss->append(input.c_str(), c - input.c_str());
            }
            ss->append(1, ' ');
        } else if(*c == '%' && (c + 2) < end
                    && isxdigit(*(c + 1)) && isxdigit(*(c + 2))){
            if(!ss) {
                ss = new std::string;
                ss->append(input.c_str(), c - input.c_str());
            }
            ss->append(1, (char)(xdigit_chars[(int)*(c + 1)] << 4 | xdigit_chars[(int)*(c + 2)]));
            c += 2;
        } else if(ss) {
            ss->append(1, *c);
        }
    }
    if(!ss) {
        return input;
    } else {
        std::string rt = *ss;
        delete ss;
        return rt;
    }
}




}